在各种系统中,可以笼统地把数据分为两方:一个是数据提供者,是服务方;另一个是数据访问者,是被服务方。数据提供者提供数据,数据访问者访问这些数据。需要注意的是,对于应用程序而言,到底是服务方还是被服务方,其身份并不是固定的,因为它有可能某一刻提供服务,下一刻又使用了别人的服务,或者既为其它程序提供数据,同时又访问其它程序的数据。
在前面章节里,使用Intent可以是向其它应用程序传递数据,这是一种”主动“的行为,即应用程序主动发起请求,通过Intent把数据传递给对方。但有的时候,应用程序希望能够访问其它应用的数据,例如访问联系人列表等,或者对数据进行一些操作,例如增、删、改、查等。那么对于其它应用程序而言,就是一种”被动”的行为。其它应用程序或者是直接响应这种请求,把数据回传给对方或执行操作,或者干脆把自己的数据文件开放给对方,让对方直接操作。显然后面这种方式是不安全的。
应用程序形成的SharedPreferences、File或数据库,一般而言只能供自己访问,并且从安全角度出发,让其它应用程序直接访问也是危险的。
那么在Android系统里面如何实现数据的共享呢?答案是ContentProvider、ContentResolver等。应用程序通过ContentProvider,提供了供外界程序对自身数据进行操纵的界面。
7.4.1.1 ContentProvider简介
ContentProvider必须实现如下方法:
boolean onCreate():用来对ContentProvider初始化。
Cursor query(Uri, String[], String, String[], String):进行数据查询。
Uri insert(Uri, ContentValues):将数据插入到ContentProvider中。
int update(Uri, ContentValues, String, String[]):更新ContentProvider中的数据。
int delete(Uri, String, String[]):删除ContentProvider中的数据。
String getType(Uri):返回ContentProvider中数据MIME类型。
7.4.1.2 Uri
1、Uri
Android系统用Uri来标识数据资源,格式如下:
content:\/\/authority\/<data_path>
Uri各部分介绍如下:
content:\/\/:协议名称,对于ContentProvider来说,协议为”content:\/\/”。
authority:授权者名字,用来确定系统中哪个ContentProvier提供资源。
data_path:数据路径,用来确定请求的数据集。如果ContentProvider提供的数据集唯一,则data_path可以省略。
系统提供了Uri类,该类有几个常用方法:
- List<String>getPathSegments():解析路径,将data_path按照”\/”分隔符进行拆分,生成每个元素类型是String的List,注意每个元素不包含“\/”。例如如果Uri的值为”content:\/\/com.sample\/words\/id”,则调用getPathSegments()后返回的结果为:{“words”,”id”}。
2、UriMatcher
给定一个Uri后,怎么确定ContentProvider能够识别它呢?Android SDK提供了UriMatcher类,使用该类可以很方便地确定Uri的类型。
UriMatcher主要有两个方法。
oid addURI(String authority, String path, int code):向UriMatcher对象添加一个Uri,新生成的Uri为:content:\/\/authority\/path,表示UriMatcher对象能够是被该Uri。如果另外一个Uri同该Uri匹配,则返回code。这样我们就可以通过返回的code来区分不同的Uri。code应该是大于等于0的值。
int match(Uri uri):返回匹配结果(code)。根据UriMatcher对象中Uri的不同返回不同的匹配结果,如果Uri同UriMatcher对象中的任何Uri都不匹配,则返回NO_MATCH(-1)。
UriMatcher的使用方法一般分为两步。
首先使用addURI()方法向UriMatcher对象添加ContentProvider能够识别的Uri及对应的匹配结果(code)。
private static final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
static {
uriMatcher.addURI(“com.sample”, “words”, 1);
uriMatcher.addURI(“com.sample”, “words/#”, 2);
}
然后使用match()方法来判断传过来的Uri对应哪个匹配结果(code),根据code不同做不同的处理。
注意:在Uri的data_path部分中,使用#表示匹配数字,*表示匹配文本。例如对于”content:\/\/com.sample\/words\/#”来说,”content:\/\/com.sample\/words\/1”、”content:\/\/com.sample\/words\/23”等都同它匹配,对应的code都是2。
switch(uriMatcher.match(uri)){
case 1:
…//处理code为1时对应的uri请求
break;
case 2:
…//处理code为2时对应的uri请求
break;
}
3、ContentUris
此外Android SDK还提供了ContentUris。该类提供了如下方法:
Uri.BuilderappendId(Uri.Builder builder, long id):在路径后面添加id,即在路径后面添加”\/id”。
static longparseId(Uri contentUri):解析路径中的id。
static Uri withAppendedId(Uri contentUri, long id):在路径后面添加id,即在路径后面添加”\/id”。
举例如下:
Uri uri=Uri.parse(“content://com.sample/words”);
Uri newuri=ContentUris.withAppendedId(Uri,3);//newuri结果为:content://com.sample/words/3
long id=parseId(newuri);//id为3
7.4.1.3 ContentResolver
ContentResolver用来对请求的Uri进行解析,确定要访问的ContentProvider。
int delete(Uri url, String where, String[] selectionArgs):删除
Uri insert(Uri url, ContentValues values):增加
int update(Uri uri, ContentValues values, String where, String[] selectionArgs):更新
Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder):查询
Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder, CancellationSignal cancellationSignal):查询
void notifyChange(Uri uri, ContentObserver observer, boolean syncToNetwork):通知注册方数据已经发生改变。
void notifyChange(Uri uri, ContentObserver observer)
用户通过Context提供的getConentResolver()方法来获得应用程序默认的ContentResolver。
7.4.1.4 数据共享示例
在此示例中,共有两个应用程序。一个是ContentProvider方,数据提供者;一个是使用ContentProvider方。这里我们仍然以单词本app为例。
1、开发ContentProvider
分为两个步骤,首先从ContentProvider派生出自己的类,然后对ContentProvider进行配置。
修改Words.java文件,添加识别的Uri对象、数据的MIME类型等。
public class Words {
public static final String AUTHORITY = "cn.edu.bistu.cs.se.wordsprovider";//URI授权者
public Words() { }
public static abstract class Word implements BaseColumns {
public static final String TABLE_NAME="words";//表名
public static final String COLUMN_NAME_WORD="word";//列:单词
public static final String COLUMN_NAME_MEANING="meaning";//列:单词含义
public static final String COLUMN_NAME_SAMPLE="sample";//列:单词示例
//MIME类型
public static final String MIME_DIR_PREFIX = "vnd.android.cursor.dir";
public static final String MIME_ITEM_PREFIX = "vnd.android.cursor.item";
public static final String MINE_ITEM = "vnd.bistu.cs.se.word";
public static final String MINE_TYPE_SINGLE = MIME_ITEM_PREFIX + "/" + MINE_ITEM;
public static final String MINE_TYPE_MULTIPLE = MIME_DIR_PREFIX + "/" + MINE_ITEM;
public static final String PATH_SINGLE = "word/#";//单条数据的路径
public static final String PATH_MULTIPLE = "word";//多条数据的路径
//Content Uri
public static final String CONTENT_URI_STRING = "content://" + AUTHORITY + "/" + PATH_MULTIPLE;
public static final Uri CONTENT_URI = Uri.parse(CONTENT_URI_STRING);
}
}
2、创建ContentProvider类
(1)在\/java\/packagename(项目中包名,示例中为en.edu.bistu.cs.se.databaseapp)上单击右键,依次选择”new”->”Other”->”Content Provider”,如图所示。
(2)系统弹出配置对话框(Android Studio存在bug,显示的是配置Activity)。如下图所示。其中选中Exported,即允许其它程序访问。
(3)Android给我们生成了一个从ContentProvider派生的类WordsProvider,同时在AndroidManifest.xml中做好了配置。先看配置文件,添加了<provoder…\/>子元素。
<provider
android:name=".WordsProvider"
android:authorities="wordsprovider.se.cs.bistu.edu.cn"
android:enabled="true"
android:exported="true"
</provider>
简单对<provoder…\/>子元素各属性进行说明。
name属性值为自定义的WordsProvider,表示ContentProvider为此类。
authorities属性值为” wordsprovider.se.cs.bistu.edu.cn”,指定该ContentProvider对应的Uri。
exported属性值为true,表示允许其它应用程序调用,即对外开发数据访问接口。
(4)再看WordsProvider.java文件。Android Studio已经给我们搭好框架,用户只需要在相应方法内填写代码即可。
public class WordsProvider extends ContentProvider {
//UriMathcher匹配结果码
private static final int MULTIPLE_WORDS = 1;
private static final int SINGLE_WORD = 2;
WordsDBHelper mDbHelper;
private static final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
static {
uriMatcher.addURI(Words.AUTHORITY, Words.Word.PATH_SINGLE, SINGLE_WORD);
uriMatcher.addURI(Words.AUTHORITY, Words.Word.PATH_MULTIPLE, MULTIPLE_WORDS);
}
public WordsProvider() { }
//删除数据
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
SQLiteDatabase db = mDbHelper.getReadableDatabase();
int count = 0;
switch (uriMatcher.match(uri)) {
case MULTIPLE_WORDS:
count = db.delete(Words.Word.TABLE_NAME, selection, selectionArgs);
break;
case SINGLE_WORD:
String whereClause=Words.Word._ID+"="+uri.getPathSegments().get(1);
count = db.delete(Words.Word.TABLE_NAME, whereClause, selectionArgs);
break;
default:
throw new IllegalArgumentException("Unkonwn Uri:" + uri);
}
//通知ContentResolver,数据已经发生改变
getContext().getContentResolver().notifyChange(uri, null);
return count;
}
//返回Uri对应的数据的MIME类型
@Override
public String getType(Uri uri) {
switch (uriMatcher.match(uri)) {
case MULTIPLE_WORDS://多条数据记录
return Words.Word.MINE_TYPE_MULTIPLE;
case SINGLE_WORD://单条数据记录
return Words.Word.MINE_TYPE_SINGLE;
default:
throw new IllegalArgumentException("Unkonwn Uri:" + uri);
}
}
@Override
public Uri insert(Uri uri, ContentValues values) {
SQLiteDatabase db = mDbHelper.getReadableDatabase();
long id = db.insert(Words.Word.TABLE_NAME, null, values);
if ( id >0 ){
//在已有的Uri后面添加id
Uri newUri = ContentUris.withAppendedId(Words.Word.CONTENT_URI, id);
getContext().getContentResolver().notifyChange(newUri, null);
return newUri;
}
throw new SQLException("Failed to insert row into " + uri);
}
@Override
public boolean onCreate() {
//创建SQLiteOpenHelper对象
mDbHelper = new WordsDBHelper(this.getContext());
return true;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
SQLiteDatabase db = mDbHelper.getReadableDatabase();
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
qb.setTables(Words.Word.TABLE_NAME);
switch (uriMatcher.match(uri)) {
case MULTIPLE_WORDS:
return db.query(Words.Word.TABLE_NAME, projection, selection,
selectionArgs, null, null, sortOrder);
case SINGLE_WORD:
qb.appendWhere(Words.Word._ID + "=" + uri.getPathSegments().get(1));
return qb.query(db, projection, selection, selectionArgs, null, null, sortOrder);
default:
throw new IllegalArgumentException("Unkonwn Uri:" + uri);
}
}
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
SQLiteDatabase db = mDbHelper.getReadableDatabase();
int count = 0;
switch (uriMatcher.match(uri)) {
case MULTIPLE_WORDS:
count = db.update(Words.Word.TABLE_NAME, values, selection, selectionArgs);
break;
case SINGLE_WORD:
String segment = uri.getPathSegments().get(1);
count = db.update(Words.Word.TABLE_NAME, values, Words.Word._ID+"="+segment, selectionArgs);
break;
default:
throw new IllegalArgumentException("Unkonwn Uri:" + uri);
}
//通知ContentResolver,数据已经发生改变
getContext().getContentResolver().notifyChange(uri, null);
return count;
}
}
}
3、使用ContentProvider
另建一个程序AccessWordsApp。为方便起见,该程序比较简单,界面上有几个按钮,分别是“全部”、“增加”、“删除”、“全部删除”、“更新”和查询,在此不给出布局文件。当按下不同的按钮时,通过ContentResolver调用ContentProvider对象来执行不同的操作,从而实现数据共享和操控。
在这里,AccessWordsApp并不知道具体是数据存放在哪里,是什么样的格式,由哪一个应用程序处理的,它仅知道发出一个Uri,在Uri中包括要操作的数据的路径信息,然后交由ContentResolver处理(insert、delete、update、query等)即可。从这里可以看出,这大大减少了应用程序之间的耦合度,提高了程序模块化程度,降低了编程难度。但是作为编程人员,一定要知道背后Android系统做了大量工作。
程序中源代码仅有一个MainActivity.java,其主要框架如下:
public class MainActivity extends AppCompatActivity {
private static final String TAG="MyWordsTag";
private ContentResolver resolver;
@Override
protected void onCreate(Bundle savedInstanceState) {
…//其它代码
resolver = this.getContentResolver();
//得到按钮
Button buttonAll=(Button)findViewById(R.id.buttonAll);
Button buttonInsert=(Button)findViewById(R.id.buttonInsert);
Button buttonDelete=(Button)findViewById(R.id.buttonDelete);
Button buttonDeleteAll=(Button)findViewById(R.id.buttonDeleteAll);
Button buttonUpdate=(Button)findViewById(R.id.buttonUpdate);
Button buttonQuery=(Button)findViewById(R.id.buttonQuery);
//为每个按钮设置监听器
buttonAll.setOnClickListener(…);
buttonInsert.setOnClickListener(…);
buttonDelete.setOnClickListener(…);
buttonDeleteAll.setOnClickListener(…);
buttonUpdate.setOnClickListener(…);
}
…//其它代码
}
“全部”按钮的监听器代码:
bttonAll.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Cursor cursor = resolver.query(Words.Word.CONTENT_URI, new String[] { Words.Word._ID,
Words.Word.COLUMN_NAME_WORD,Words.Word.COLUMN_NAME_MEANING,Words.Word.COLUMN_NAME_SAMPLE},
null, null, null);
if (cursor == null){
Toast.makeText(MainActivity.this,"没有找到记录",Toast.LENGTH_LONG).show();
return;
}
//找到记录,这里简单起见,使用Log输出
String msg = "";
if (cursor.moveToFirst()){
do{
msg += "ID:" + cursor.getInt(cursor.getColumnIndex(Words.Word._ID)) + ",";
msg += "单词:" + cursor.getString(cursor.getColumnIndex(Words.Word.COLUMN_NAME_WORD))+ ",";
msg += "含义:" + cursor.getInt(cursor.getColumnIndex(Words.Word.COLUMN_NAME_MEANING)) + ",";
msg += "示例" + cursor.getFloat(cursor.getColumnIndex(Words.Word.COLUMN_NAME_SAMPLE)) + "\n";
} while(cursor.moveToNext());
}
Log.v(TAG,msg);
}
});
“增加”按钮的监听器代码:
buttonInsert.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
String strWord="Banana";
String strMeaning="banana";
String strSample="This banana is very nice.";
ContentValues values = new ContentValues();
values.put(Words.Word.COLUMN_NAME_WORD, strWord);
values.put(Words.Word.COLUMN_NAME_MEANING, strMeaning);
values.put(Words.Word.COLUMN_NAME_SAMPLE, strSample);
Uri newUri = resolver.insert(Words.Word.CONTENT_URI, values);
}
});
“删除”按钮的监听器代码:
buttonDelete.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
String id="3";//简单起见,这里指定ID,用户可在程序中设置id的实际值
Uri uri = Uri.parse(Words.Word.CONTENT_URI_STRING + "/" + id);
int result = resolver.delete(uri, null, null);
}
});
“全部删除”按钮的监听器代码:
buttonDeleteAll.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
resolver.delete(Words.Word.CONTENT_URI, null, null);
}
});
“更新”按钮的监听器代码:
buttonUpdate.setOnClickListener(new View.OnClickListener() {
@Override public void onClick(View view) {
String id="3";
String strWord="Banana";
String strMeaning="banana";
String strSample="This banana is very nice.";
ContentValues values = new ContentValues();
values.put(Words.Word.COLUMN_NAME_WORD, strWord);
values.put(Words.Word.COLUMN_NAME_MEANING, strMeaning);
values.put(Words.Word.COLUMN_NAME_SAMPLE, strSample);
Uri uri = Uri.parse(Words.Word.CONTENT_URI_STRING + "/" + id);
int result = resolver.update(uri, values, null, null);
}
});
“查询”按钮的监听器代码:
buttonQuery.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
String id="3"; Uri uri = Uri.parse(Words.Word.CONTENT_URI_STRING + "/" + id);
Cursor cursor = resolver.query(Words.Word.CONTENT_URI,
new String[] { Words.Word._ID, Words.Word.COLUMN_NAME_WORD,
Words.Word.COLUMN_NAME_MEANING,Words.Word.COLUMN_NAME_SAMPLE},
null, null, null);
if (cursor == null){
Toast.makeText(MainActivity.this,"没有找到记录",Toast.LENGTH_LONG).show();
return;
}
//找到记录,这里简单起见,使用Log输出
String msg = "";
if (cursor.moveToFirst()){
do{
msg += "ID:" + cursor.getInt(cursor.getColumnIndex(Words.Word._ID)) + ",";
msg += "单词:" + cursor.getString(cursor.getColumnIndex(Words.Word.COLUMN_NAME_WORD))+ ","; /b>;
msg += "含义:" + cursor.getInt(cursor.getColumnIndex(Words.Word.COLUMN_NAME_MEANING)) + ","; b>;
msg += "示例" + cursor.getFloat(cursor.getColumnIndex(Words.Word.COLUMN_NAME_SAMPLE)) + "\n";
}while(cursor.moveToNext());
}
Log.v(TAG,msg);
}
});
在本例中,修改、删除、查找等只能根据id来进行,请思考并修改根据Word字段内容来进行上述操作。
单击“全部”按钮时在Logcat中显示结果。